package com.hzgj.bcl.soa.cicada.lb;

import com.hzgj.bcl.soa.cicada.router.MutableRouter;
import com.hzgj.bcl.soa.cicada.sender.Sender;
import com.hzgj.bcl.soa.config.ReferenceDef;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.Set;

import lombok.Getter;
import lombok.Setter;

/**
 * A basic implementation of the load balancer where an arbitrary list of
 * servers can be set as the server pool. A ping can be set to determine the
 * liveness of a server. Internally, this class maintains an "all" server list
 * and an "up" server list and use them depending on what the caller asks for.
 */
public class BaseLoadBalancer implements LoadBalancer {

    private static final String DEFAULT_NAME = "default";
    private static Logger logger = LoggerFactory.getLogger(BaseLoadBalancer.class);
    protected Map<String, Rule> rules;
    @Getter
    @Setter
    protected String name = DEFAULT_NAME;
    @Getter
    private MutableRouter router;

    public BaseLoadBalancer(String name, Map<String, Rule> rules, MutableRouter router) {
        if (logger.isDebugEnabled()) {
            logger.debug("LoadBalancer [{}]:  initialized", name);
        }
        this.name = name;
        this.router = router;
        this.rules = rules;
        rules.values().forEach(rule -> rule.setLoadBalancer(this));
    }

    public BaseLoadBalancer(Map<String, Rule> rules, MutableRouter router) {
        this(DEFAULT_NAME, rules, router);
    }

    @Override
    public void start() {
        router.start();
    }

    @Override
    public void stop() {
        router.stop();
    }

    @Override
    public Sender chooseSender(Object key) {
        Rule.RuleObject lbObject = (Rule.RuleObject) key;
        Rule rule = rules.get(lbObject.getRule());
        if (rule == null) {
            return null;
        } else {
            try {
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn(String.format("LoadBalancer [%s]:  Error choosing rule %s for key %s", name, rule, key), e);
                return null;
            }
        }
    }

    @Override
    public void markSenderDown(Sender sender, String service) {
        ReferenceDef referenceDef = new ReferenceDef();
        referenceDef.setService(service);
        Set<Sender> senders = router.getUpSenders(referenceDef);
        if (senders.size() > 1) {
            router.markDown(service, sender);
            logger.info("retry other node from " + sender);
        } else {
            if (!senders.contains(sender)) {
                router.markDown(service, sender);
                logger.info("retry other node from " + sender);
            } else {
                String message = "can't retry other node only one node exist of " + sender;
                logger.info(message);
                throw new RuntimeException(message);
            }
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("{NFLoadBalancer:name=").append(this.getName())
                .append(",Load balancer stats=").append("}");
        return sb.toString();
    }
}
